package com.zbkj.service.service.impl;

import cn.hutool.core.util.StrUtil;
import com.zbkj.common.constants.Constants;
import com.zbkj.common.exception.CrmebException;
import com.zbkj.common.vo.ChunkUploadVo;
import com.zbkj.common.vo.ChunkUploadResultVo;
import com.zbkj.common.vo.CloudVo;
import com.zbkj.common.vo.FileResultVo;
import com.zbkj.common.utils.DateUtil;
import com.zbkj.common.utils.UploadUtil;
import com.zbkj.common.model.system.SystemAttachment;
import com.zbkj.common.config.CrmebConfig;
import com.zbkj.service.service.*;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.model.*;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 大文件上传服务实现类
 * +----------------------------------------------------------------------
 * | CRMEB [ CRMEB赋能开发者，助力企业发展 ]
 * +----------------------------------------------------------------------
 * | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
 * +----------------------------------------------------------------------
 * | Licensed CRMEB并不是自由软件，未经许可不能去掉CRMEB相关版权
 * +----------------------------------------------------------------------
 * | Author: CRMEB Team <admin@crmeb.com>
 * +----------------------------------------------------------------------
 */
@Service
public class BigFileUploadServiceImpl implements BigFileUploadService {

    private static final Logger logger = LoggerFactory.getLogger(BigFileUploadServiceImpl.class);

    @Autowired
    private SystemConfigService systemConfigService;

    @Autowired
    private SystemAttachmentService systemAttachmentService;

    @Autowired
    private CrmebConfig crmebConfig;

    // 存储上传进度的内存缓存
    private static final Map<String, Set<Integer>> uploadedChunksCache = new ConcurrentHashMap<>();
    private static final Map<String, ChunkUploadVo> uploadInfoCache = new ConcurrentHashMap<>();

    @Override
    public ChunkUploadResultVo checkFileExists(String identifier) {
        ChunkUploadResultVo result = new ChunkUploadResultVo();
        result.setIdentifier(identifier);
        result.setCompleted(false);

        // 检查是否已有上传记录
        Set<Integer> uploadedChunks = uploadedChunksCache.get(identifier);
        ChunkUploadVo uploadInfo = uploadInfoCache.get(identifier);

        if (uploadedChunks != null && uploadInfo != null) {
            result.setUploadedChunks(uploadedChunks.size());
            result.setTotalChunks(uploadInfo.getTotalChunks());
            result.setProgress((double) uploadedChunks.size() / uploadInfo.getTotalChunks() * 100);
            result.setUploadedChunkNumbers(new ArrayList<>(uploadedChunks));
            result.setMessage("找到已上传的分片");
        } else {
            result.setUploadedChunks(0);
            result.setProgress(0.0);
            result.setMessage("文件不存在，可以开始上传");
        }

        return result;
    }

    @Override
    public ChunkUploadResultVo uploadChunk(MultipartFile file, ChunkUploadVo chunkUploadVo) throws IOException {
        if (file == null || file.isEmpty()) {
            throw new CrmebException("上传的分片文件不能为空");
        }

        String identifier = chunkUploadVo.getIdentifier();
        Integer chunkNumber = chunkUploadVo.getChunkNumber();

        // 创建临时目录
        String tempDir = getTempDir(identifier);
        Path tempDirPath = Paths.get(tempDir);
        if (!Files.exists(tempDirPath)) {
            Files.createDirectories(tempDirPath);
        }

        // 保存分片文件
        String chunkFileName = chunkNumber + ".chunk";
        Path chunkFilePath = tempDirPath.resolve(chunkFileName);
        file.transferTo(chunkFilePath.toFile());

        // 更新上传进度
        uploadedChunksCache.computeIfAbsent(identifier, k -> new HashSet<>()).add(chunkNumber);
        uploadInfoCache.put(identifier, chunkUploadVo);

        Set<Integer> uploadedChunks = uploadedChunksCache.get(identifier);
        
        ChunkUploadResultVo result = new ChunkUploadResultVo();
        result.setIdentifier(identifier);
        result.setChunkNumber(chunkNumber);
        result.setUploadedChunks(uploadedChunks.size());
        result.setTotalChunks(chunkUploadVo.getTotalChunks());
        result.setProgress((double) uploadedChunks.size() / chunkUploadVo.getTotalChunks() * 100);
        result.setCompleted(uploadedChunks.size() == chunkUploadVo.getTotalChunks());
        result.setMessage("分片 " + chunkNumber + " 上传成功");

        logger.info("分片上传成功: identifier={}, chunkNumber={}, progress={}%", 
                   identifier, chunkNumber, result.getProgress());

        return result;
    }

    @Override
    public ChunkUploadResultVo mergeChunks(ChunkUploadVo chunkUploadVo) throws IOException {
        String identifier = chunkUploadVo.getIdentifier();
        Set<Integer> uploadedChunks = uploadedChunksCache.get(identifier);

        if (uploadedChunks == null || uploadedChunks.size() != chunkUploadVo.getTotalChunks()) {
            throw new CrmebException("分片上传不完整，无法合并文件");
        }

        String tempDir = getTempDir(identifier);
        Path tempDirPath = Paths.get(tempDir);

        // 验证所有分片都存在
        for (int i = 0; i < chunkUploadVo.getTotalChunks(); i++) {
            Path chunkPath = tempDirPath.resolve(i + ".chunk");
            if (!Files.exists(chunkPath)) {
                throw new CrmebException("分片文件缺失: " + i);
            }
        }

        // 合并文件
        String fileName = chunkUploadVo.getFilename();
        String extName = FilenameUtils.getExtension(fileName);
        
        if (fileName.length() > 99) {
            fileName = StrUtil.subPre(fileName, 90).concat(".").concat(extName);
        }

        // 创建最终文件路径
        String newFileName = UploadUtil.fileName(extName);
        String modelPath = "public/" + chunkUploadVo.getModel() + "/";
        String webPath = Constants.UPLOAD_TYPE_FILE + "/" + modelPath + 
                        DateUtil.nowDate(Constants.DATE_FORMAT_DATE).replace("-", "/") + "/";
        
        String rootPath = crmebConfig.getImagePath().replace(" ", "").replace("//", "/");
        String destPath = FilenameUtils.separatorsToSystem(rootPath + webPath) + newFileName;
        
        // 创建目标文件目录
        File destFile = new File(destPath);
        destFile.getParentFile().mkdirs();

        // 合并分片
        try (FileOutputStream fos = new FileOutputStream(destFile)) {
            for (int i = 0; i < chunkUploadVo.getTotalChunks(); i++) {
                Path chunkPath = tempDirPath.resolve(i + ".chunk");
                Files.copy(chunkPath, fos);
            }
        }

        logger.info("文件合并完成: {}", destPath);

        // 根据uploadType配置选择上传方式
        String uploadType = systemConfigService.getValueByKeyException("uploadType");
        Integer uploadTypeInt = Integer.parseInt(uploadType);
        
        FileResultVo fileResult;
        
        switch (uploadTypeInt) {
            case 1:
                // 本地存储
                fileResult = createLocalFileResult(destFile, webPath + newFileName, fileName, chunkUploadVo);
                break;
            case 4:
                // 腾讯云COS
                fileResult = uploadToTencentCloud(destFile, webPath + newFileName, fileName, chunkUploadVo);
                break;
            default:
                // 默认本地存储
                logger.warn("不支持的上传类型: {}, 使用本地存储", uploadTypeInt);
                fileResult = createLocalFileResult(destFile, webPath + newFileName, fileName, chunkUploadVo);
                break;
        }

        // 清理临时文件
        cleanupTempFiles(identifier);

        ChunkUploadResultVo result = new ChunkUploadResultVo();
        result.setIdentifier(identifier);
        result.setCompleted(true);
        result.setProgress(100.0);
        result.setFileResult(fileResult);
        result.setMessage("文件上传完成");

        return result;
    }

    @Override
    public ChunkUploadResultVo getUploadProgress(String identifier) {
        Set<Integer> uploadedChunks = uploadedChunksCache.get(identifier);
        ChunkUploadVo uploadInfo = uploadInfoCache.get(identifier);

        ChunkUploadResultVo result = new ChunkUploadResultVo();
        result.setIdentifier(identifier);

        if (uploadedChunks != null && uploadInfo != null) {
            result.setUploadedChunks(uploadedChunks.size());
            result.setTotalChunks(uploadInfo.getTotalChunks());
            result.setProgress((double) uploadedChunks.size() / uploadInfo.getTotalChunks() * 100);
            result.setCompleted(uploadedChunks.size() == uploadInfo.getTotalChunks());
            result.setUploadedChunkNumbers(new ArrayList<>(uploadedChunks));
        } else {
            result.setUploadedChunks(0);
            result.setProgress(0.0);
            result.setCompleted(false);
        }

        return result;
    }

    @Override
    public void cancelUpload(String identifier) {
        // 清理内存缓存
        uploadedChunksCache.remove(identifier);
        uploadInfoCache.remove(identifier);
        
        // 清理临时文件
        cleanupTempFiles(identifier);
        
        logger.info("取消上传，清理完成: {}", identifier);
    }

    /**
     * 创建本地文件结果
     */
    private FileResultVo createLocalFileResult(File file, String webPath, String originalFileName, 
                                             ChunkUploadVo chunkUploadVo) throws IOException {
        // 保存文件记录到数据库
        SystemAttachment systemAttachment = new SystemAttachment();
        systemAttachment.setName(originalFileName);
        systemAttachment.setSattDir(webPath);
        systemAttachment.setAttSize(String.valueOf(file.length()));
        systemAttachment.setAttType(FilenameUtils.getExtension(originalFileName));
        systemAttachment.setImageType(1); // 本地存储
        systemAttachment.setPid(chunkUploadVo.getPid());
        systemAttachmentService.save(systemAttachment);

        // 构建返回结果
        FileResultVo fileResult = new FileResultVo();
        fileResult.setFileName(originalFileName);
        fileResult.setFileSize(file.length());
        fileResult.setExtName(FilenameUtils.getExtension(originalFileName));
        fileResult.setUrl(webPath);
        fileResult.setType(chunkUploadVo.getType());

        logger.info("文件保存到本地: {}", webPath);
        return fileResult;
    }

    /**
     * 上传文件到腾讯云
     */
    private FileResultVo uploadToTencentCloud(File file, String webPath, String originalFileName, 
                                            ChunkUploadVo chunkUploadVo) throws IOException {
        
        // 获取腾讯云配置
        String pre = "tx";
        CloudVo cloudVo = new CloudVo();
        cloudVo.setDomain(systemConfigService.getValueByKeyException(pre + "UploadUrl"));
        cloudVo.setAccessKey(systemConfigService.getValueByKeyException(pre + "AccessKey"));
        cloudVo.setSecretKey(systemConfigService.getValueByKeyException(pre + "SecretKey"));
        cloudVo.setBucketName(systemConfigService.getValueByKeyException(pre + "StorageName"));
        cloudVo.setRegion(systemConfigService.getValueByKeyException(pre + "StorageRegion"));

        // 初始化COS客户端
        COSCredentials cred = new BasicCOSCredentials(cloudVo.getAccessKey(), cloudVo.getSecretKey());
        ClientConfig clientConfig = new ClientConfig(new com.qcloud.cos.region.Region(cloudVo.getRegion()));
        clientConfig.setConnectionTimeout(30 * 60 * 1000);  // 30分钟连接超时
        clientConfig.setSocketTimeout(60 * 60 * 1000);      // 60分钟读取超时
        COSClient cosClient = new COSClient(cred, clientConfig);

        try {
            // 检查存储桶是否存在
            if (!cosClient.doesBucketExist(cloudVo.getBucketName())) {
                CreateBucketRequest createBucketRequest = new CreateBucketRequest(cloudVo.getBucketName());
                createBucketRequest.setCannedAcl(CannedAccessControlList.Private);
                cosClient.createBucket(createBucketRequest);
            }

            // 大文件使用分片上传到腾讯云
            if (file.length() > 100 * 1024 * 1024) { // 大于100MB使用分片上传
                uploadLargeFileToTencent(cosClient, cloudVo.getBucketName(), webPath, file);
            } else {
                // 小文件直接上传
                PutObjectRequest putObjectRequest = new PutObjectRequest(cloudVo.getBucketName(), webPath, file);
                cosClient.putObject(putObjectRequest);
            }

            logger.info("文件上传到腾讯云成功: {}", webPath);

            // 保存文件记录到数据库
            SystemAttachment systemAttachment = new SystemAttachment();
            systemAttachment.setName(originalFileName);
            systemAttachment.setSattDir(webPath);
            systemAttachment.setAttSize(String.valueOf(file.length()));
            systemAttachment.setAttType(FilenameUtils.getExtension(originalFileName));
            systemAttachment.setImageType(4); // 腾讯云COS
            systemAttachment.setPid(chunkUploadVo.getPid());
            systemAttachmentService.save(systemAttachment);

            // 构建返回结果 - 返回腾讯云URL
            FileResultVo fileResult = new FileResultVo();
            fileResult.setFileName(originalFileName);
            fileResult.setFileSize(file.length());
            fileResult.setExtName(FilenameUtils.getExtension(originalFileName));
            fileResult.setUrl(cloudVo.getDomain() + "/" + webPath); // 返回完整的腾讯云URL
            fileResult.setType(chunkUploadVo.getType());

            // 删除本地临时文件
            String fileIsSave = systemConfigService.getValueByKeyException("file_is_save");
            if (!"1".equals(fileIsSave)) {
                file.delete();
            }

            return fileResult;

        } finally {
            cosClient.shutdown();
        }
    }

    /**
     * 大文件分片上传到腾讯云
     */
    private void uploadLargeFileToTencent(COSClient cosClient, String bucketName, String key, File file) {
        try {
            // 初始化分片上传
            InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, key);
            InitiateMultipartUploadResult initResponse = cosClient.initiateMultipartUpload(initRequest);
            String uploadId = initResponse.getUploadId();

            // 分片大小设置为10MB
            long partSize = 10 * 1024 * 1024;
            long fileLength = file.length();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }

            List<PartETag> partETags = new ArrayList<>();

            // 上传分片
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;

                UploadPartRequest uploadRequest = new UploadPartRequest()
                        .withBucketName(bucketName)
                        .withKey(key)
                        .withUploadId(uploadId)
                        .withPartNumber(i + 1)
                        .withFileOffset(startPos)
                        .withFile(file)
                        .withPartSize(curPartSize);

                UploadPartResult uploadResult = cosClient.uploadPart(uploadRequest);
                partETags.add(uploadResult.getPartETag());

                logger.info("腾讯云分片上传进度: {}/{}", i + 1, partCount);
            }

            // 完成分片上传
            CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(
                    bucketName, key, uploadId, partETags);
            cosClient.completeMultipartUpload(compRequest);

            logger.info("腾讯云大文件分片上传完成: {}", key);

        } catch (Exception e) {
            logger.error("腾讯云分片上传失败: {}", e.getMessage(), e);
            throw new CrmebException("腾讯云上传失败: " + e.getMessage());
        }
    }

    /**
     * 获取临时目录路径
     */
    private String getTempDir(String identifier) {
        String tempRoot = System.getProperty("java.io.tmpdir");
        return tempRoot + File.separator + "upload_chunks" + File.separator + identifier;
    }

    /**
     * 清理临时文件
     */
    private void cleanupTempFiles(String identifier) {
        try {
            String tempDir = getTempDir(identifier);
            Path tempDirPath = Paths.get(tempDir);
            
            if (Files.exists(tempDirPath)) {
                Files.walk(tempDirPath)
                     .sorted(Comparator.reverseOrder())
                     .map(Path::toFile)
                     .forEach(File::delete);
            }
            
            logger.info("临时文件清理完成: {}", tempDir);
        } catch (IOException e) {
            logger.warn("清理临时文件失败: {}", e.getMessage());
        }
    }
} 